<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="UTF-8">
  <meta http-equiv="X-UA-Compatible" content="IE=edge">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <title>test</title>
</head>
<body>
  <button>按钮</button>

  <script>
    // 示例一：（参数复用）
    function url_currying(protocol) {
        return function(hostname, pathname) {
          return `${protocol}${hostname}${pathname}`
        }
    }
    const url_https = url_currying('https://')
    console.log(url_https('domain.com', '/dashboard')) // "https://domain.com/dashboard"

    // 示例二
    const addEvent = (function() {
      if(window.addEventListener) {
            /**
             * element: 需要添加事件监听的元素
             * type: 为元素添加什么类型的事件
             * listener: 执行的回调函数
             * useCapture: 要进行事件冒泡或者事件捕获的选择
             */ 
          return function(element, type, listener, useCapture) {
              element.addEventListener(type, function(e){
                  listener.call(element, e) // 用 call 进行 this 的绑定
                }, useCapture)
            }
        } else if(window.attachEvent) {
            // IE（IE9 之前） 支持的是 attachEvent 方法
            // 不需要第四个参数，因为 IE 支持的是事件冒泡
          return function(element, type, listener) {
              element.attachEvent('on'+type, function(e) {
                  listener.call(element, e)
                })
            }
        }
    })()                      
    // 测试
    let button = document.querySelector('button');
    addEvent(button, 'click', (e) => {console.log('点击了 div');}, true);


    /** 示例三
     * 实现一个 add 方法
     * add(1, 2, 3)
     * add(1, 2)(3)
     * add(1)(2)(3)(4)(5)
     */ 
    function add() {
      // 第一次执行时，arguments 对象转化为数组
      let args = Array.prototype.slice.call( arguments )

      // 内部声明一个函数，利用闭包的特性，保存args，并且收集所有的参数值
      let next = function() {
        args.push( ...arguments )
        return next
      }

      // 当返回值是一个函数时，自动隐士转换为字符串
      // 利用隐士转换的规则，修改toString方法的内部逻辑
      next.toString = function() {
        return args.reduce(function(total, current) { 
          return total + current
        }, 0)
      }
      return next
    }
    console.log( add(1)(2, 5)(3)(4) )         // ƒ () { args.push( ...arguments ) return next }
    console.log( typeof add(1)(2, 5)(3)(4) )  // function
    console.log( +add(1)(2, 5)(3)(4) )        // 15

    /**
     * 重写函数 fn 的 toString 方法能够改变函数的隐式转换结果
     * 重写函数 fn 的 valueOf 方法也能够改变函数的隐式转换结果
     * 同时重写函数的toString方法与valueOf方法时，最终的结果会取valueOf方法的返回结果
     */   
    function fn() { return 20; }
    fn.valueOf = function() { return 60 }
    fn.toString = function() { return 30 }
    console.log( fn + 10 ); // 70

    // currying 原理
    // 内部返回一个函数
    // 收集参数
    // 判断 fn.length 原函数的形参 与 args.length 的长度
    // args.length >= fn.length 执行返回
    // args.length < fn.length 递归执行 currying
    function currying(fn) {
      return function() {
        let args = Array.prototype.slice.call(arguments)
        if(args.length >= fn.length) {
          return fn.call(this, args)
        }
        return currying.call(this, args)
      }
    }

    function add
 </script>
</body>
</html>